﻿using FluentAssertions;
using CollabApp.Domain.Entities.Auth;

namespace CollabApp.Domain.Tests;

/// <summary>
/// 用户实体测试类
/// </summary>
public class UserEntityTests
{
    [Fact]
    public void Create_ValidInput_ShouldCreateUserWithCorrectProperties()
    {
        // Arrange
        var username = "testuser";
        var password = "password123";
        var nickname = "TestNickname";

        // Act
        var user = User.Create(username, password, nickname);

        // Assert
        user.Should().NotBeNull();
        user.Id.Should().NotBeEmpty();
        user.Username.Should().Be(username);
        user.Nickname.Should().Be(nickname);
        user.Status.Should().Be(UserStatus.Active);
        user.TokenStatus.Should().Be(TokenStatus.None);
        user.RememberMe.Should().BeFalse();
        user.IsDeleted.Should().BeFalse();
        user.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
        user.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Theory]
    [InlineData("", "password123", "nickname")]
    [InlineData(" ", "password123", "nickname")]
    [InlineData(null, "password123", "nickname")]
    public void Create_EmptyUsername_ShouldThrowArgumentException(string username, string password, string nickname)
    {
        // Act & Assert
        Action act = () => User.Create(username, password, nickname);
        act.Should().Throw<ArgumentException>().WithMessage("用户名不能为空*");
    }

    [Theory]
    [InlineData("username", "", "nickname")]
    [InlineData("username", " ", "nickname")]
    [InlineData("username", null, "nickname")]
    public void Create_EmptyPassword_ShouldThrowArgumentException(string username, string password, string nickname)
    {
        // Act & Assert
        Action act = () => User.Create(username, password, nickname);
        act.Should().Throw<ArgumentException>().WithMessage("密码不能为空*");
    }

    [Theory]
    [InlineData("username", "password123", "")]
    [InlineData("username", "password123", " ")]
    [InlineData("username", "password123", null)]
    public void Create_EmptyNickname_ShouldThrowArgumentException(string username, string password, string nickname)
    {
        // Act & Assert
        Action act = () => User.Create(username, password, nickname);
        act.Should().Throw<ArgumentException>().WithMessage("昵称不能为空*");
    }

    [Fact]
    public void VerifyPassword_CorrectPassword_ShouldReturnTrue()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        
        // Act
        var result = user.VerifyPassword("password123");
        
        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void VerifyPassword_IncorrectPassword_ShouldReturnFalse()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        
        // Act
        var result = user.VerifyPassword("wrongpassword");
        
        // Assert
        result.Should().BeFalse();
    }

    [Theory]
    [InlineData("")]
    [InlineData(" ")]
    [InlineData(null)]
    public void VerifyPassword_EmptyPassword_ShouldReturnFalse(string password)
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        
        // Act
        var result = user.VerifyPassword(password);
        
        // Assert
        result.Should().BeFalse();
    }

    [Fact]
    public void UpdateProfile_ValidInput_ShouldUpdateNicknameAndAvatar()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "oldnickname");
        var newNickname = "newnickname";
        var avatarUrl = "https://example.com/avatar.jpg";

        // Act
        user.UpdateProfile(newNickname, avatarUrl);

        // Assert
        user.Nickname.Should().Be(newNickname);
        user.AvatarUrl.Should().Be(avatarUrl);
    }

    [Theory]
    [InlineData("")]
    [InlineData(" ")]
    [InlineData(null)]
    public void UpdateProfile_EmptyNickname_ShouldThrowArgumentException(string nickname)
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");

        // Act & Assert
        Action act = () => user.UpdateProfile(nickname);
        act.Should().Throw<ArgumentException>().WithMessage("昵称不能为空*");
    }

    [Fact]
    public void UpdatePassword_ValidPassword_ShouldUpdatePasswordHash()
    {
        // Arrange
        var user = User.Create("testuser", "oldpassword", "nickname");
        var oldPasswordHash = user.PasswordHash;
        var oldPasswordSalt = user.PasswordSalt;

        // Act
        user.UpdatePassword("newpassword");

        // Assert
        user.PasswordHash.Should().NotBe(oldPasswordHash);
        user.PasswordSalt.Should().NotBe(oldPasswordSalt);
        user.VerifyPassword("newpassword").Should().BeTrue();
        user.VerifyPassword("oldpassword").Should().BeFalse();
    }

    [Theory]
    [InlineData("")]
    [InlineData(" ")]
    [InlineData(null)]
    public void UpdatePassword_EmptyPassword_ShouldThrowArgumentException(string password)
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");

        // Act & Assert
        Action act = () => user.UpdatePassword(password);
        act.Should().Throw<ArgumentException>().WithMessage("密码不能为空*");
    }

    [Fact]
    public void SetTokens_ValidInput_ShouldSetAllTokenProperties()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        var accessToken = "access_token_123";
        var refreshToken = "refresh_token_123";
        var accessExpires = DateTime.UtcNow.AddMinutes(30);
        var refreshExpires = DateTime.UtcNow.AddDays(7);
        var deviceInfo = "{\"device\":\"mobile\"}";

        // Act
        user.SetTokens(accessToken, refreshToken, accessExpires, refreshExpires, true, deviceInfo);

        // Assert
        user.AccessToken.Should().Be(accessToken);
        user.RefreshToken.Should().Be(refreshToken);
        user.AccessTokenExpiresAt.Should().Be(accessExpires);
        user.RefreshTokenExpiresAt.Should().Be(refreshExpires);
        user.RememberMe.Should().BeTrue();
        user.TokenStatus.Should().Be(TokenStatus.Active);
        user.DeviceInfo.Should().Be(deviceInfo);
        user.LastActivityAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
        user.LastLoginAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void RefreshAccessToken_ValidInput_ShouldUpdateAccessTokenAndActivity()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        var oldActivityTime = DateTime.UtcNow.AddMinutes(-10);
        user.SetTokens("old_access", "refresh_token", DateTime.UtcNow.AddMinutes(30), DateTime.UtcNow.AddDays(7));
        
        var newAccessToken = "new_access_token";
        var newExpires = DateTime.UtcNow.AddMinutes(60);

        // Act
        user.RefreshAccessToken(newAccessToken, newExpires);

        // Assert
        user.AccessToken.Should().Be(newAccessToken);
        user.AccessTokenExpiresAt.Should().Be(newExpires);
        user.TokenStatus.Should().Be(TokenStatus.Active);
        user.LastActivityAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void RevokeTokens_WithReason_ShouldClearTokensAndSetStatus()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        user.SetTokens("access_token", "refresh_token", DateTime.UtcNow.AddMinutes(30), DateTime.UtcNow.AddDays(7));
        var reason = "User logout";

        // Act
        user.RevokeTokens(reason);

        // Assert
        user.AccessToken.Should().BeNull();
        user.RefreshToken.Should().BeNull();
        user.AccessTokenExpiresAt.Should().BeNull();
        user.RefreshTokenExpiresAt.Should().BeNull();
        user.TokenStatus.Should().Be(TokenStatus.Revoked);
        user.TokenRevokedReason.Should().Be(reason);
        user.TokenRevokedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void UpdateActivity_ShouldUpdateLastActivityTime()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        var oldActivity = user.LastActivityAt;

        // Act
        user.UpdateActivity();

        // Assert
        user.LastActivityAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
        user.LastActivityAt.Should().NotBe(oldActivity);
    }

    [Fact]
    public void Ban_ShouldSetStatusToBannedAndRevokeTokens()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        user.SetTokens("access_token", "refresh_token", DateTime.UtcNow.AddMinutes(30), DateTime.UtcNow.AddDays(7));

        // Act
        user.Ban();

        // Assert
        user.Status.Should().Be(UserStatus.Banned);
        user.TokenStatus.Should().Be(TokenStatus.Revoked);
        user.TokenRevokedReason.Should().Be("用户被封禁");
        user.AccessToken.Should().BeNull();
        user.RefreshToken.Should().BeNull();
    }

    [Fact]
    public void Unban_ShouldSetStatusToActive()
    {
        // Arrange
        var user = User.Create("testuser", "password123", "nickname");
        user.Ban();

        // Act
        user.Unban();

        // Assert
        user.Status.Should().Be(UserStatus.Active);
    }

    [Fact]
    public void VerifyPassword_StaticMethod_ValidPassword_ShouldReturnTrue()
    {
        // Arrange
        var password = "testpassword";
        var (hash, salt) = User.CreatePasswordHash(password);

        // Act
        var result = User.VerifyPassword(password, hash, salt);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public void VerifyPassword_StaticMethod_InvalidPassword_ShouldReturnFalse()
    {
        // Arrange
        var password = "testpassword";
        var (hash, salt) = User.CreatePasswordHash(password);

        // Act
        var result = User.VerifyPassword("wrongpassword", hash, salt);

        // Assert
        result.Should().BeFalse();
    }

    [Theory]
    [InlineData("", "hash", "salt")]
    [InlineData("password", "", "salt")]
    [InlineData("password", "hash", "")]
    [InlineData(null, "hash", "salt")]
    [InlineData("password", null, "salt")]
    [InlineData("password", "hash", null)]
    public void VerifyPassword_StaticMethod_EmptyInputs_ShouldReturnFalse(string password, string hash, string salt)
    {
        // Act
        var result = User.VerifyPassword(password, hash, salt);

        // Assert
        result.Should().BeFalse();
    }

    [Fact]
    public void CreatePasswordHash_ValidPassword_ShouldReturnHashAndSalt()
    {
        // Arrange
        var password = "testpassword";

        // Act
        var (hash, salt) = User.CreatePasswordHash(password);

        // Assert
        hash.Should().NotBeNullOrEmpty();
        salt.Should().NotBeNullOrEmpty();
        hash.Should().NotBe(password);
        salt.Should().NotBe(password);
        
        // 验证生成的哈希是正确的
        User.VerifyPassword(password, hash, salt).Should().BeTrue();
    }

    [Theory]
    [InlineData("")]
    [InlineData(" ")]
    [InlineData(null)]
    public void CreatePasswordHash_EmptyPassword_ShouldThrowArgumentException(string password)
    {
        // Act & Assert
        Action act = () => User.CreatePasswordHash(password);
        act.Should().Throw<ArgumentException>().WithMessage("密码不能为空*");
    }
}
